package forth

import (
	"bytes"
	"io"
	"reflect"
)

type Token interface{}

type NestSys int

type ExecutionToken interface {
	Name() string
	RunOn(StackVM)
	Location() int // data field
}

type ExecutionOnToken func(Token)

// Stack is a LIFO data structure holding non-nil tokens
type Stack interface {
	// Push the token onto the stack
	Push(Token)
	// Pop the token from the stack
	Pop() Token
}

type DataSpace interface {
	Fetch(int) Token
	Store(int, Token)
	Reserved() int
	Capacity() int
	Reserve(int)
}

type Syscall interface {
	OpenFile(string) (int, int)
	OpenDir(string) (int, int)
	ReadDir(int, int, int) (int, int, int)
	Close(int) int
}

type InputSourceSpec interface {
	Id() int
	Buffer() *bytes.Buffer
	Refill() bool
}

type StackVM interface {
	Stack
	ReturnStack() Stack
	ControlStack() Stack
	DataSpace() DataSpace
	// Input provides a byte buffer to read from
	Input() InputSourceSpec
	// Output provides a byte writer
	Output() io.Writer
	// Register a new word with empty or specified instruction set
	Register(string, ...func(StackVM))
	// Append tokens to the new word or throw error if there is no open word
	Append(...Token)
	// Find the execution token in the dictionary by name
	Find(string) (ExecutionToken, bool, bool)
	// Delay the execution to process the next token
	Delay(ExecutionOnToken)
	// Goto stops the current execution and jumps to the specified location
	Goto(int)
	// Execute the tokens in data space by given address
	Execute(int, int)
	Abort()
	// SetInput sets the new input source
	SetInput(Token)
	// ResetInput resets the last input source
	ResetInput()
	// Syscall returns the syscall interface
	Syscall() Syscall
}

func IsInstruction(token Token) bool {
	typ := reflect.TypeOf(token)
	if typ.Kind() != reflect.Func {
		return false
	}
	if typ.NumIn() != 1 {
		return false
	}
	if typ.In(0).Name() != "StackVM" {
		return false
	}
	return true
}

func TryExecute(token Token, vm StackVM) {
	if xt, ok := token.(ExecutionToken); ok {
		xt.RunOn(vm)
	} else if IsInstruction(token) {
		token.(func(StackVM))(vm)
	} else {
		vm.Push(token)
	}
}
